/* File: MagnifyView.m - View class for 'Magnify'
 *
 * By: Christopher Lane
 * Symbolic Systems Resources Group
 * Knowledge Systems Laboratory
 * Stanford University
 *
 * Date:  9 November 1992
 *
 * Copyright: 1990, 1991 & 1992 by The Leland Stanford Junior University.
 * This program may be distributed without restriction for non-commercial use.
 */
 
#import "Magnify.h"
#import "MagnifyView.h"
#import "NXCursorPatch.h"
#import "DefaultsTable.h"

#import <appkit/Pasteboard.h>

#define GRIDLINEMODE NX_COPY
#define GRIDLINEWIDTH ((float) 1.0)

#define NX_CMYKMASK	(NX_MONOTONICMASK | NX_COLORMASK)

#define NX_EMPTYMASK (NX_COMMANDMASK & (~ NX_COMMANDMASK))

#define MAGNIFYLEVEL (100)
#define NXSetWindowLevel _NXSetWindowLevel
extern int NXSetWindowLevel(int, int);

@implementation MagnifyView

- initFrame:(const NXRect *) frameRect;
{
	[super initFrame:frameRect];

	frozen = NO;
	lock = mutex_alloc();
	flags = NX_EMPTYMASK;
	mouse = frameRect->origin;
	
	scale = getFloatDefault("Scale");
	showGrid = getBoolDefault("ShowGrid");
	gridSize = getFloatDefault("GridSize");
	showCursor = getBoolDefault("ShowCursor");

	return [self createWindows];
}

- (BOOL) acceptsFirstResponder { return YES; }

- copy:sender
{
	char *buffer;
	NXStream *stream;
	int length, maxLength;
	Pasteboard *pasteboard = [Pasteboard new];
	
	[pasteboard declareTypes:&NXPostScriptPboardType num:1 owner:self];

	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[self copyPSCodeInside:&bounds to:stream];
	NXFlush(stream);

	NXGetMemoryBuffer(stream, &buffer, &length, &maxLength);
	[pasteboard writeType:NXPostScriptPboard data:buffer length:length];
	NXCloseMemory(stream, NX_FREEBUFFER);
		
	return self;
}

- drawSelf:(const NXRect *) rects :(int) rectCount
{
	mutex_lock(lock); {
		NXDrawBitmap(&virtualBounds, pixelsWide, pixelsHigh, bitsPerSample, samplesPerPixel, bitsPerPixel, bytesPerRow, 
			(BOOL) (planarConfig == NX_PLANAR),
			(BOOL) ((photoInt & NX_ALPHAMASK) == NX_ALPHAMASK),
			colorSpace, data);
		if(showGrid) [self drawGrid];
		if(showCursor) [self drawCursor];
		} mutex_unlock(lock);

	return self;
}

- free
{
	unsigned int i;
	
	if (invisibleWindow != nil) [invisibleWindow close];

	for(i = 0; i < NX_MAXPLANES; i++) if (data[i] != NULL) free(data[i]);
	
	return [super free];
}

- sizeTo:(NXCoord) newWidth :(NXCoord) newHeight;
{	
	id result;
	
	mutex_lock(lock); {
		result = [super sizeTo:newWidth :newHeight];
		} mutex_unlock(lock);
	
	[self createWindows];
	
	return result;
}

- mouseMoved:(NXEvent *)theEvent
{
	int eventMask;
	
	if (mutex_try_lock(lock)) {
		eventMask = [window removeFromEventMask:NX_MOUSEMOVEDMASK]; {
			while([NXApp peekAndGetNextEvent:NX_MOUSEMOVEDMASK] != NULL);

			mouse = theEvent->location;
			[window convertBaseToScreen:&mouse];

			} [window setEventMask:eventMask];
			
		mutex_unlock(lock);
			
		[[self updateBitmap:&mouse] display];
		}

	return [nextResponder mouseMoved:theEvent];
}

- flagsChanged:(NXEvent *)theEvent
{
	flags = theEvent->flags;

	return [nextResponder flagsChanged:theEvent];
}

- drawCursor
{
	NXSize size;
	NXPoint point; 
	NXImage *image;
	NXCursor *cursor;
	const NXPoint *hotSpot;
	
	if((cursor = [NXCursor currentCursor]) == nil) return nil;
	
	[(image = [cursor image]) getSize:&size];
		
    hotSpot = [cursor hotSpot];
	
	point.x = rint((bounds.size.width + scale) * HALF) - hotSpot->x; 
	point.y = rint((bounds.size.height + scale) * HALF) - (size.height + hotSpot->y);

	[image composite:NX_SOVER toPoint:&point];

	return self;
}

- drawGrid
{	
	float x, y, max;
	
	max = bounds.origin.y + bounds.size.height;
	for(y = bounds.origin.y; y < max; y += gridSize)
		PScompositerect(bounds.origin.x, y, bounds.size.width, GRIDLINEWIDTH, GRIDLINEMODE);
		
	max = bounds.origin.x + bounds.size.width;
	for(x = bounds.origin.x; x < max; x += gridSize)
		PScompositerect(x, bounds.origin.y, GRIDLINEWIDTH, bounds.size.height, GRIDLINEMODE);
		
	return self;
}

- updateBitmap:(NXPoint *) point
{
	NXRect rect;
	
	mutex_lock(lock); {

		[invisibleView getFrame:&rect];
#ifdef DEBUG
		fprintf(stderr, "NXReadBitmap(rect:{%f,%f}, wide:%d, high:%d, bps:%d, spp:%d)\n",
			rect.size.width, rect.size.height, pixelsWide, pixelsHigh, bitsPerSample, samplesPerPixel);
#endif
		[invisibleWindow moveTo:point->x - offset.width :point->y - offset.height];

		[invisibleWindow orderFront:self]; {
			(void) [invisibleView lockFocus]; {
				NXReadBitmap(&rect, pixelsWide, pixelsHigh, bitsPerSample, samplesPerPixel, planarConfig, photoInt, data[0], data[1], data[2], data[3], data[4]);
				} [invisibleView unlockFocus];
			} [invisibleWindow orderOut:self];
	
		} mutex_unlock(lock);
	
	bitsPerPixel = bitsPerSample * ((planarConfig == NX_PLANAR) ? 1 : samplesPerPixel);
	bytesPerRow = (7 + pixelsWide * bitsPerSample * ((planarConfig == NX_PLANAR) ? 1 : samplesPerPixel)) / 8;
	colorSpace = ((photoInt & NX_CMYKMASK) == NX_CMYKMASK) ? NX_CMYKColorSpace : (photoInt & NX_CMYKMASK);
	
	return self;
}

- (int) flags { return flags; }

- (NXPoint *) mouse { return &mouse; }

- (float) scale { return scale; }

- setScale:(float) value
{
	mutex_lock(lock); {
		scale = value;
		} mutex_unlock(lock);
	
	return self;
}

- (float) gridSize { return gridSize; }

- setGridSize:(float) value
{
	mutex_lock(lock); {
		gridSize = value;
		} mutex_unlock(lock);
	
	return self;
}

- (BOOL) isFrozen { return frozen; }

- setFrozen:(BOOL) state
{
	mutex_lock(lock); {
		if (frozen = state) (void) [window removeFromEventMask:NX_MOUSEMOVEDMASK];
		else (void) [window addToEventMask:NX_MOUSEMOVEDMASK];
		} mutex_unlock(lock);
	
	return self;
}

- setShowGrid:(BOOL) state
{
	mutex_lock(lock); {
		showGrid = state;
		} mutex_unlock(lock);

	return [self display];
}

- setShowCursor:(BOOL) state
{
	mutex_lock(lock); {
		showCursor = state;
		} mutex_unlock(lock);

	return [self display];
}

- createWindows
{
	NXRect rect;
	size_t size;
	unsigned int i;
	
	mutex_lock(lock); {
	
		rect = bounds;
		
		offset.width = (rect.size.width = sizeof(int) * ceil((bounds.size.width / scale) / sizeof(int))) * HALF;
		offset.height = (rect.size.height = ceil(bounds.size.height / scale)) * HALF;

		if (invisibleWindow != nil) [invisibleWindow sizeWindow:rect.size.width :rect.size.height];
		else {
			invisibleWindow = [[Window alloc] initContent:&rect style:NX_PLAINSTYLE backing:NX_NONRETAINED buttonMask:NX_NOBUTTONS defer:NO];
			PSsetautofill(NO, [invisibleWindow windowNum]);
			NXSetWindowLevel([invisibleWindow windowNum], MAGNIFYLEVEL);
			[invisibleWindow setEventMask:NX_NULLEVENTMASK];
			[(invisibleView = [invisibleWindow contentView]) allocateGState];
			}

		[invisibleView getFrame:&rect];

		virtualBounds = rect;
		virtualBounds.size.width *= scale;
		virtualBounds.size.height *= scale;
		
		(void) [invisibleView lockFocus]; {
			NXSizeBitmap(&rect, (int *) &size, &pixelsWide, &pixelsHigh, &bitsPerSample, &samplesPerPixel, &planarConfig, &photoInt);
			} [invisibleView unlockFocus];
#ifdef DEBUG
		fprintf(stderr, "NXSizeBitmap(rect:{%f,%f}, wide:%d, high:%d, bps:%d, spp:%d)\n",
			rect.size.width, rect.size.height, pixelsWide, pixelsHigh, bitsPerSample, samplesPerPixel);
#endif
		for(i = 0; i < NX_MAXPLANES; i++) {
			if(data[i] == NULL) data[i] = malloc(size);
			else if (size > malloc_size(data[i])) data[i] = realloc(data[i], size);
			if(planarConfig == NX_MESHED) break;
			}
	
		} mutex_unlock(lock);

	return [[self updateBitmap:&mouse] display];
}

void timer(DPSTimedEntry teNumber, double now, id self)
{
	if (![self isFrozen]) {
		NXPoint *mouse = [self mouse];
		Window *window = [self window];
		
		[window getMouseLocation:mouse];
		[window convertBaseToScreen:mouse];
		[[self updateBitmap:mouse] display];
		}
}

@end
